package com.ecr.hub.service.user.impl;

import java.util.Date;
import java.util.List;

import javax.annotation.Resource;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpSession;

import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;

import com.ecr.hub.ApplicationException;
import com.ecr.hub.DaoApplicationException;
import com.ecr.hub.database.dao.IDaoUser;
import com.ecr.hub.front.bean.listener.SessionListener;
import com.ecr.hub.model.user.Role;
import com.ecr.hub.model.user.User;
import com.ecr.hub.service.user.IUserManager;
import com.ecr.hub.util.EncryptionUtils;

@Service("userManager")
public class UserManagerImpl implements IUserManager {

	// Liste des rôles.
	private List<Role> lstRoles;

	@Resource(name = "daoUser")
	IDaoUser daoUser;

	/**
	 * Liste l'ensemble des utilisateurs, on prend tous les utilisateurs dont le
	 * champ "active" est égal à "true".
	 */
	@Override
	public List<User> getAllList() throws ApplicationException {
		try {
			return daoUser.getAllList();
		} catch (EmptyResultDataAccessException e) {
			return null;
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Création d'un nouvel utilisateur, ici, on ne se pose aucune question, le
	 * mot de passe est crypté, on place le cryptage dans le champ confirmation
	 * afin que l'administrateur puisse avoir le même nombre de caractères dans
	 * les deux champs au niveau de la fiche utilisateur (dans le cas ou il
	 * revient sur la fiche pour effectuer une modification). On positionne la
	 * demande de saisie d'un nouveau mot de pase à "true" pour que
	 * l'utilisateur puisse le modifier à la prochaine connexion.
	 */
	@Override
	public void create(User user) throws ApplicationException {
		try {
			this.completeUserRole(user);
			user.setCreationDate(new Date());
			user.setPassword(EncryptionUtils.encryptePassword(
					user.getPassword(), user.getLogin()));
			user.setPasswordConfirm(user.getPassword());
			user.setId(daoUser.create(user));
			user.setLastName(user.getLastName().toUpperCase());
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Mise à jour de l'utilisateur. L'idée est de savoir si le mot de passe a
	 * été modifié par l'administrateur. On recherche l'utilisateur initial en
	 * base de données avec son identifiant. On compare le mot de passe crypté
	 * trouvé en base avec celui non crypté de l'utilisateur sur lequel
	 * travaille l'administateur. Si le mot de passe non crypté correspond, cela
	 * signifie que le mot de passe n'a pas été changé.
	 */
	@Override
	public void update(User user) throws ApplicationException {
		try {
			User userBdd = daoUser.getById(user.getId());
			user.setLastName(user.getLastName().toUpperCase());
			this.completeUserRole(user);
			if (!user.getPassword().equals(userBdd.getPassword())) {
				user.setPassword(EncryptionUtils.encryptePassword(
						user.getPassword(), user.getLogin()));
				user.setPasswordConfirm(user.getPassword());
				user.setAskForPassword(true);
			}
			daoUser.update(user);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Suppression d'un utilisateur, on se contente juste de passer le booleen
	 * d'affichage à "false". Un utilisateur n'est jamais supprimé physiquement
	 * de la base de données.
	 */
	@Override
	public void delete(User user) throws ApplicationException {
		try {
			daoUser.delete(user);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Récupération de l'ensemble des rôles présents dans la base de données.
	 */
	@Override
	public List<Role> getAllRoles() throws ApplicationException {
		try {
			return daoUser.getAllRoles();
		} catch (EmptyResultDataAccessException e) {
			return null;
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * A partir de la mire de connexion, récupération des informations
	 * utilisateur à partir de son identifiant et de son mot de passe. Pour
	 * récupérer le mot de passe en base de données, on crypte celui entré sur
	 * la mire de connexion par l'utilisateur et on le compare à celui récupéré
	 * dans la bdd.
	 */
	@Override
	public User connect(String login, String password)
			throws ApplicationException {
		try {
			HttpSession session = (HttpSession) FacesContext
					.getCurrentInstance().getExternalContext()
					.getSession(false);

			return daoUser.connect(login,
					EncryptionUtils.encryptePassword(password, login),
					session.getId());
		} catch (EmptyResultDataAccessException e) {
			return null;
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * 
	 */
	@Override
	public void completeUserRole(User user) throws ApplicationException {
		try {
			if (null == lstRoles)
				lstRoles = daoUser.getAllRoles();
			for (Role role : lstRoles) {
				if (role.getId() == user.getRole().getId()) {
					user.getRole().setRole(role.getRole());
					user.getRole().setLabel(role.getLabel());
					break;
				}
			}
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Lors de la primo-connexion, l'utilisateur doit choisir un nouveau mot de
	 * passe pour pouvoir préserver la confidentialité de sa connexion.
	 */
	@Override
	public void updatePassword(User user) throws ApplicationException {
		try {
			user.setPassword(EncryptionUtils.encryptePassword(
					user.getPassword(), user.getLogin()));
			user.setAskForPassword(false);
			daoUser.updatePassword(user);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Indique l'utilisateur comme deconnecté de l'application dans la base de
	 * données.
	 */
	@Override
	public void disconnect(User user) throws ApplicationException {

		try {
			((HttpSession) FacesContext.getCurrentInstance()
					.getExternalContext().getSession(true)).invalidate();
			user.setSessionId(null);
			daoUser.disconnect(user);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Force la connexion utilisateur. Deconnecte tous les utilisateurs qui ont
	 * des rôles permettant la modification des données. Si on ne trouve pas la
	 * session utilisateur, alors on ne lance pas d'exception, en effet, il se
	 * peut que l'utilisateur se soit deconnecté depuis longtemps et que la
	 * session en temps que tel n'existe plus, il ne reste plus que la trace en
	 * base de données.
	 */
	@Override
	public void force(User user) throws ApplicationException {

		try {
			List<User> lstUsers = daoUser.getListConnected();
			for (User userConnected : lstUsers) {
				if (user.getId() != userConnected.getId()) {
					HttpSession session = SessionListener.sessionMap
							.get(userConnected.getSessionId());
					if (session != null) {
						session.invalidate();
					}
					userConnected.setSessionId(null);
					daoUser.disconnect(userConnected);
				}
			}
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}
}
